<?php
namespace Utils;
use PDO;
class Mapper {
    private $dbms = DBMS; // 数据库类型
    private $host = HOSTNAME; // 主机名
    private $user = USERNAME; // 用户名
    private $pass = PASSWORD; // 密码
    private $dbName = DATABASE; // 数据库名
    private $charset = CHARSET; // 数据库编码
    private $dbh;
    /**
     * 构造函数，初始化PDO
     */
    public function __construct() {
        // 转换成小写
        $this->dbms = strtolower($this->dbms);
        $dsn = "$this->dbms:host=$this->host;dbname=$this->dbName;charset=$this->charset";
        try {
            // 初始化一个PDO 对象
            $this->dbh = new PDO($dsn, $this->user, $this->pass, array(
                PDO::ATTR_PERSISTENT => true
            ));
            $this->dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
            /**
             * PDO::ERRMODE_SILENT： 默认模式，不主动报错，需要主动以 $pdo->errorInfo()的形式获取错误信息。
             * PDO::ERRMODE_WARNING: 引发 E_WARNING 错误，主动报错
             * PDO::ERRMODE_EXCEPTION: 主动抛出 exceptions 异常，需要以try{}cath(){}输出错误信息。
             */
            $this->dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
            // 关闭string转换
            $this->dbh->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false);
            // 关闭模拟prepare
            $this->dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
        }
        catch(PDOException $e) {
            //print_r("数据库连接出错！<br/>");
            return false;
        }
    }
    /**
     * 析构函数，释放PDO对象
     * @param
     * @return
     */
    function __destruct() {
        $this->dbh = null;
    }
    //原生query
    function query($sql) {
        if (!$this->dbh) {
            echo json_encode([-1, "数据加载错误！"], JSON_UNESCAPED_UNICODE);
            exit();
        }
        $this->dbh->query($sql);
    }
    //执行sql 查询
    public function select($sql, $dataArray = []) {
        if (!$this->dbh) {
            echo json_encode([-1, "数据加载错误！"], JSON_UNESCAPED_UNICODE);
            exit();
        }
        $stmt = $this->dbh->prepare($sql);
        $this->dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
        foreach ($dataArray as $key => $value) {
            // 支持用 :xxx 和 ? 占位符的方式绑定参数
            $param = is_numeric($key) ? $key + 1 : ':' . $key;
            // 绑定参数，根据参数类型选择绑定的类型。在sql语句“limit ?,?”中，参数必须绑定为整数，否则会报错
            // 当参数为整数字符串，且数字未溢出
            if (is_numeric($value) && stripos($value, '.') === false && $value > PHP_INT_MIN && $value < PHP_INT_MAX) {
                $stmt->bindValue($param, $value, PDO::PARAM_INT);
            } else if (is_bool($value)) {
                $stmt->bindValue($param, $value, PDO::PARAM_BOOL);
            } else if (is_array($value)) {
                // 如果是数组，则支持 sql 用? 占位符的方式绑定数组。
                $replace = "(?)";
                for ($i = 1; $i < count($value); $i++) {
                    // 动态添加占位符 ?
                    $replace = substr_replace($replace, ",?", -1, 0);
                }
                // 替换原来的sql 语句
                $sql = str_replace("(?)", $replace, $sql);
                // 准备sql 语句
                $stmt = $this->dbh->prepare($sql);
                foreach ($value as $item) {
                    // 绑定参数
                    $stmt->bindValue($param, $item);
                    $param++;
                }
            } else {
                $stmt->bindValue($param, $value, $this->selectDataType($value));
            }
        }
        if ($stmt->execute()) {
            return $stmt->fetchAll(PDO::FETCH_CLASS);
        } else {
            //print_r($stmt->errorInfo());
            echo json_encode([-1, "数据加载错误！"], JSON_UNESCAPED_UNICODE);
            exit();
        }
    }
    /**
     * 执行sql操纵,不支持数组参数
     * @param string $sql
     * @param array $dataArray
     * @return int
     */
    public function run($sql, $dataArray = []) {
        if (!$this->dbh) return -1;
        $stmt = $this->dbh->prepare($sql);
        $this->dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
        foreach ($dataArray as $key => $value) {
            // 支持用 :xxx 和 ? 占位符的方式绑定参数
            $param = is_numeric($key) ? $key + 1 : ':' . $key;
            // 绑定参数，根据参数类型选择绑定的类型。在sql语句“limit ?,?”中，参数必须绑定为整数，否则会报错
            // 当参数为整数字符串，且数字未溢出
            if (is_numeric($value) && stripos($value, '.') === false && $value > PHP_INT_MIN && $value < PHP_INT_MAX) {
                $stmt->bindValue($param, $value, PDO::PARAM_INT);
            } else if (is_bool($value)) {
                $stmt->bindValue($param, $value, PDO::PARAM_BOOL);
            } else {
                $stmt->bindValue($param, $value, $this->selectDataType($value));
            }
        }
        // Log::DEBUG($stmt->queryString);
        if ($stmt->execute()) {
            return $stmt->rowCount() === false ? 0 : 1;
        } else {
            return -1;
        }
    }
    public function update($sql, $dataArray = []) {
        $Array = Mapper::editSort($sql, $dataArray);
        return $this->run($sql, $Array);
    }
    public function insert($sql, $dataArray = []) {
        $Array = Mapper::addSort($sql, $dataArray);
        return $this->run($sql, $Array);
    }
    /**
     * 数组作为参数的数据操纵，可实现in操作，如：executeHasIn("delete from tb_guide where gid in(?)",[[91,92,93]]);
     * @param $sql
     * @param array $dataArray
     * @return int
     */
    public function delete($sql, $dataArray = []) {
        if (!$this->dbh) return -1;
        foreach ($dataArray as $key => $value) {
            $param = $key + 1;
            if (is_array($value)) {
                // 如果是数组，则支持 sql 用? 占位符的方式绑定数组。
                $replace = "(?)";
                for ($i = 1; $i < count($value); $i++) {
                    // 动态添加占位符 ?
                    $replace = substr_replace($replace, ",?", -1, 0);
                }
                // 替换原来的sql 语句
                $sql = str_replace("(?)", $replace, $sql);
                // 准备sql 语句
                $stmt = $this->dbh->prepare($sql);
                foreach ($value as $item) {
                    // 绑定参数
                    $stmt->bindValue($param, $item);
                    $param++;
                }
            } else {
                return -1;
            }
        }
        if ($stmt->execute()) {
            return $stmt->rowCount();
        } else {
            return -1;
        }
    }
    /**
     * 获取自增的主键
     * @return string
     */
    public function getLastInsertId() {
        if (!$this->dbh) return -1;
        return $this->dbh->lastInsertId();
    }
    //自动选择类型
    public function selectDataType($val) {
        if (is_bool($val)) {
            return PDO::PARAM_BOOL;
        } elseif (is_int($val)) {
            return PDO::PARAM_INT;
        } elseif (is_null($val)) {
            return PDO::PARAM_NULL;
        } elseif (is_string($val)) {
            return PDO::PARAM_STR;
        } else {
            return PDO::PARAM_LOB;
        }
    }
    //微通封装sql更新编辑寻值替换排序算法
    public static function editSort($sql, $data) {
        preg_match_all('/(?i)set([\w\W]*?)(?i)where/', $sql, $preList);
        preg_match_all('/(?i)where([\w\W]*?)=/', $sql, $idList);
        $pre = $preList[1][0];
        preg_match_all('/\040([\w\W]*?)=/', $pre, $resultList);
        $id = $idList[1][0];
        $id = trim($id);
        $result = $resultList[1];
        $ids = '';
        foreach ($result as $index => & $str) {
            foreach ($data as $key => $val) {
                if ($id == trim($key)) {
                    $ids = $val;
                }
                if (trim(str_replace('`', '', $str)) == trim($key)) { //对``符号过滤
                    $str = $val;
                }
            }
        }
        array_push($result, $ids);
        //print_r($result);die();
        return $result;
    }
    //微通封装sql录入添加寻值替换排序算法
    public static function addSort($sql, $data) {
        preg_match_all('/\(([\w\W]*?)(?i)values/', $sql, $preList);
        $pre = $preList[1][0];
        $end = preg_replace('/\)/i', "", $pre);
        $end = trim($end);
        $result = explode(',', $end);
        foreach ($result as $index => & $str) {
            foreach ($data as $key => $val) {
                if (trim(str_replace('`', '', $str)) == trim($key)) {
                    $str = $val;
                }
            }
        }
        return $result;
    }
}
